#include <stdlib.h>
#include <string.h>
#include "util.h"

#include "hosted_hw_drv.h"
#include "spi_drv.h"
#include "netdev_api.h"
#include "vhci_if.h"
#include "adapter.h"

#include "hosted/hosted.h"

#define CLEANUP_CTRL_MSG(msg)                                                                      \
	do {                                                                                       \
		if (msg) {                                                                         \
			if (msg->free_buffer_handle) {                                             \
				if (msg->free_buffer_func) {                                       \
					msg->free_buffer_func(msg->free_buffer_handle);            \
					msg->free_buffer_handle = NULL;                            \
				}                                                                  \
			}                                                                          \
			hosted_free(msg);                                                          \
			msg = NULL;                                                                \
		}                                                                                  \
	} while (0);

typedef struct {
	int event;
	ctrl_resp_cb_t fun;
} event_callback_table_t;

K_SEM_DEFINE(hosted_sync_sem, 0, 1);
static sys_slist_t hosted_callback_list;
static uint8_t hosted_init_status = 0;

static void reset_slave(void);
static void spi_driver_event_handler(uint8_t event);

int hosted_init()
{
	reset_slave();
	network_init();	 // init virturl network interface for wifi
	ble_vhci_init(); // init virturl hci interface for ble
	spi_link_init(spi_driver_event_handler);
	hosted_init_status = 1;
	return SUCCESS;
}

bool hosted_is_inited(void)
{
	return hosted_init_status == 1;
}

bool hosted_event_is_set(uint32_t events)
{
	sys_slist_t *list = &hosted_callback_list;
	struct hosted_event_callback *cb, *tmp;
	SYS_SLIST_FOR_EACH_CONTAINER_SAFE (list, cb, tmp, node) {
		if (cb->events & events) {
			return true;
		}
	}
	return false;
}

int hosted_add_callback(struct hosted_event_callback *hosted_event_cb)
{
	sys_slist_prepend(&hosted_callback_list, &hosted_event_cb->node);
	return 0;
}

static inline void hosted_fire_callbacks(sys_slist_t *list, uint32_t events)
{
	struct hosted_event_callback *cb, *tmp;

	SYS_SLIST_FOR_EACH_CONTAINER_SAFE (list, cb, tmp, node) {
		if (cb->events & events) {
			cb->handler(cb->events & events, cb->arg);
		}
	}
}

int hosted_sync_set(bool set)
{
	if (set) {
		k_sem_give(&hosted_sync_sem);
	}
	return 0;
}

int hosted_wait_sync(k_timeout_t timeout)
{
	return k_sem_take(&hosted_sync_sem, timeout); // Fixme: here maybe lead to infinite block
						      // !!!
}

bool hosted_is_synced()
{
	if (k_sem_count_get(&hosted_sync_sem) == 0) {
		int handshake_status = hosted_gpio_read_handshake();
		int dataready_status = hosted_gpio_read_dataready();
		if (handshake_status == 1 &&
		    dataready_status ==
			    1) { // Detect initialization signal(generated by esp32c3 at startup)
			return false;
		}
	}
	return true;
}

static int ctrl_app_event_callback(ctrl_cmd_t *app_event)
{
	if (!app_event || (app_event->msg_type != CTRL_EVENT)) {
		if (app_event)
			printk("Msg type is not event[%u]\n\r", app_event->msg_type);
		goto fail_parsing;
	}

	if ((app_event->msg_id <= CTRL_EVENT_BASE) || (app_event->msg_id >= CTRL_EVENT_MAX)) {
		printk("Event Msg ID[%u] is not correct\n\r", app_event->msg_id);
		goto fail_parsing;
	}

	switch (app_event->msg_id) {

	case CTRL_EVENT_ESP_INIT: {
		printk("App EVENT: ESP INIT\n\r");
		hosted_fire_callbacks(&hosted_callback_list, HOSTED_LINK_EVT_ACTIVE);
		break;
	}
	case CTRL_EVENT_HEARTBEAT: {
		printk("App EVENT: Heartbeat event [%u]\n\r", app_event->u.e_heartbeat.hb_num);
		break;
	}
	case CTRL_EVENT_STATION_DISCONNECT_FROM_AP: {
		printk("App EVENT: Station mode: Disconnect Reason[%u]\n\r",
		       app_event->resp_event_status);
		hosted_fire_callbacks(&hosted_callback_list, HOSTED_WIFI_EVT_STA_DISCONNECTED);
		break;
	}
	case CTRL_EVENT_STATION_CONNECT_FROM_ESP_SOFTAP: {
		char *p = app_event->u.e_sta_disconnected.mac;
		if (p && strlen(p)) {
			printk("App EVENT: SoftAP mode: Connect MAC[%s]\n\r", p);
		}
		hosted_fire_callbacks(&hosted_callback_list, HOSTED_WIFI_EVT_AP_STACONNECTED);
		break;
	}
	case CTRL_EVENT_STATION_DISCONNECT_FROM_ESP_SOFTAP: {
		char *p = app_event->u.e_sta_disconnected.mac;
		if (p && strlen(p)) {
			printk("App EVENT: SoftAP mode: Disconnect MAC[%s]\n\r", p);
		}
		hosted_fire_callbacks(&hosted_callback_list, HOSTED_WIFI_EVT_AP_STADISCONNECTED);
		break;
	}
	default: {
		printk("Invalid event[%u] to parse\n\r", app_event->msg_id);
		break;
	}
	}
	CLEANUP_CTRL_MSG(app_event);
	return SUCCESS;

fail_parsing:
	CLEANUP_CTRL_MSG(app_event);
	return FAILURE;
}

int register_event_callbacks(void)
{
	int ret = SUCCESS;
	int evt = 0;

	event_callback_table_t events[] = {
		{CTRL_EVENT_ESP_INIT, ctrl_app_event_callback},
		{CTRL_EVENT_HEARTBEAT, ctrl_app_event_callback},
		{CTRL_EVENT_STATION_DISCONNECT_FROM_AP, ctrl_app_event_callback},
		{CTRL_EVENT_STATION_DISCONNECT_FROM_ESP_SOFTAP, ctrl_app_event_callback},
		{CTRL_EVENT_STATION_CONNECT_FROM_ESP_SOFTAP, ctrl_app_event_callback},
	};

	for (evt = 0; evt < sizeof(events) / sizeof(event_callback_table_t); evt++) {
		if (CALLBACK_SET_SUCCESS !=
		    set_event_callback(events[evt].event, events[evt].fun)) {
			printk("event callback register failed for event[%u]\n\r",
			       events[evt].event);
			ret = FAILURE;
			break;
		}
	}
	return ret;
}

int unregister_event_callbacks(void)
{
	int ret = SUCCESS;
	int evt = 0;
	for (evt = CTRL_EVENT_BASE + 1; evt < CTRL_EVENT_MAX; evt++) {
		if (CALLBACK_SET_SUCCESS != reset_event_callback(evt)) {
			printk("reset event callback failed for event[%u]\n\r", evt);
			ret = FAILURE;
		}
	}
	return ret;
}

static inline void control_path_init(void)
{
	if (init_hosted_control_lib()) {
		printk("init hosted control lib failed\n\r");
		return;
	}
	register_event_callbacks();
}

static void reset_slave(void)
{
	hosted_gpio_init();
	hosted_gpio_write_reset(0);

	k_msleep(4);
	hosted_gpio_write_reset(1);

	/* stop spi transactions short time to avoid slave sync issues */
	hosted_wait_sync(K_SECONDS(CONFIG_HOSTED_INIT_TIMEOUT));
}

static void spi_driver_event_handler(uint8_t event)
{
	switch (event) {
	case SPI_DRIVER_ACTIVE: {
		/* Initiate control path now */
		control_path_init(); // init control path and control task
		// hosted_fire_callbacks(&hosted_callback_list, HOSTED_LINK_EVT_ACTIVE);
		break;
	}
	default:
		break;
	}
}
